/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "src/gpu/ganesh/GrResourceProvider.h"

#include "include/core/SkAlphaType.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkData.h"
#include "include/core/SkRect.h"
#include "include/core/SkSize.h"
#include "include/gpu/GpuTypes.h"
#include "include/gpu/GrBackendSurface.h"
#include "include/gpu/GrTypes.h"
#include "include/private/base/SingleOwner.h"
#include "include/private/base/SkTemplates.h"
#include "src/base/SkMathPriv.h"
#include "src/core/SkMipmap.h"
#include "src/gpu/BufferWriter.h"
#include "src/gpu/ResourceKey.h"
#include "src/gpu/SkBackingFit.h"
#include "src/gpu/ganesh/GrAttachment.h"
#include "src/gpu/ganesh/GrCaps.h"
#include "src/gpu/ganesh/GrDataUtils.h"
#include "src/gpu/ganesh/GrGpu.h"
#include "src/gpu/ganesh/GrGpuBuffer.h"
#include "src/gpu/ganesh/GrGpuResourcePriv.h"
#include "src/gpu/ganesh/GrImageInfo.h"
#include "src/gpu/ganesh/GrPixmap.h"
#include "src/gpu/ganesh/GrRenderTarget.h"
#include "src/gpu/ganesh/GrResourceCache.h"
#include "src/gpu/ganesh/GrSemaphore.h"
#include "src/gpu/ganesh/GrSurface.h"
#include "src/gpu/ganesh/GrTexture.h"

#include <algorithm>
#include <utility>

struct SkImageInfo;

using namespace skia_private;

#define ASSERT_SINGLE_OWNER SKGPU_ASSERT_SINGLE_OWNER(fSingleOwner)

GrResourceProvider::GrResourceProvider(GrGpu *gpu, GrResourceCache *cache, skgpu::SingleOwner *owner)
    : fCache(cache),
      fGpu(gpu)
#ifdef SK_DEBUG
      ,
      fSingleOwner(owner)
#endif
{
    fCaps = sk_ref_sp(fGpu->caps());
}

sk_sp<GrTexture> GrResourceProvider::createTexture(SkISize dimensions, const GrBackendFormat &format,
    GrTextureType textureType, GrColorType colorType, GrRenderable renderable, int renderTargetSampleCnt,
    skgpu::Budgeted budgeted, skgpu::Mipmapped mipmapped, GrProtected isProtected, const GrMipLevel texels[],
    std::string_view label)
{
    ASSERT_SINGLE_OWNER

    if (this->isAbandoned()) {
        return nullptr;
    }

    int numMipLevels = 1;
    if (mipmapped == skgpu::Mipmapped::kYes) {
        numMipLevels = SkMipmap::ComputeLevelCount(dimensions.fWidth, dimensions.fHeight) + 1;
    }

    if (!fCaps->validateSurfaceParams(dimensions, format, renderable, renderTargetSampleCnt, mipmapped, textureType)) {
        return nullptr;
    }
    // Current rule is that you can provide no level data, just the base, or all the levels.
    bool hasPixels = texels[0].fPixels;
    auto scratch = this->getExactScratch(dimensions, format, textureType, renderable, renderTargetSampleCnt, budgeted,
        mipmapped, isProtected, label);
    if (scratch) {
        if (!hasPixels) {
            return scratch;
        }
        return this->writePixels(std::move(scratch), colorType, dimensions, texels, numMipLevels);
    }
    AutoSTArray<14, GrMipLevel> tmpTexels;
    AutoSTArray<14, std::unique_ptr<char[]>> tmpDatas;
    GrColorType tempColorType = GrColorType::kUnknown;
    if (hasPixels) {
        tempColorType = this->prepareLevels(format, colorType, dimensions, texels, numMipLevels, &tmpTexels, &tmpDatas);
        if (tempColorType == GrColorType::kUnknown) {
            return nullptr;
        }
    }
    return fGpu->createTexture(dimensions, format, textureType, renderable, renderTargetSampleCnt, budgeted,
        isProtected, colorType, tempColorType, tmpTexels.get(), numMipLevels, label);
}

sk_sp<GrTexture> GrResourceProvider::getExactScratch(SkISize dimensions, const GrBackendFormat &format,
    GrTextureType textureType, GrRenderable renderable, int renderTargetSampleCnt, skgpu::Budgeted budgeted,
    skgpu::Mipmapped mipmapped, GrProtected isProtected, std::string_view label)
{
    sk_sp<GrTexture> tex(this->findAndRefScratchTexture(dimensions, format, textureType, renderable,
        renderTargetSampleCnt, mipmapped, isProtected, label));
    if (tex && skgpu::Budgeted::kNo == budgeted) {
        tex->resourcePriv().makeUnbudgeted();
    }

    return tex;
}

sk_sp<GrTexture> GrResourceProvider::createTexture(SkISize dimensions, const GrBackendFormat &format,
    GrTextureType textureType, GrColorType colorType, GrRenderable renderable, int renderTargetSampleCnt,
    skgpu::Budgeted budgeted, SkBackingFit fit, GrProtected isProtected, const GrMipLevel &mipLevel,
    std::string_view label)
{
    ASSERT_SINGLE_OWNER

    if (!mipLevel.fPixels) {
        return nullptr;
    }

    if (SkBackingFit::kApprox == fit) {
        if (this->isAbandoned()) {
            return nullptr;
        }
        if (!fCaps->validateSurfaceParams(dimensions, format, renderable, renderTargetSampleCnt, skgpu::Mipmapped::kNo,
            textureType)) {
            return nullptr;
        }

        auto tex = this->createApproxTexture(dimensions, format, textureType, renderable, renderTargetSampleCnt,
            isProtected, label);
        if (!tex) {
            return nullptr;
        }
        return this->writePixels(std::move(tex), colorType, dimensions, &mipLevel, 1);
    } else {
        return this->createTexture(dimensions, format, textureType, colorType, renderable, renderTargetSampleCnt,
            budgeted, skgpu::Mipmapped::kNo, isProtected, &mipLevel, label);
    }
}

sk_sp<GrTexture> GrResourceProvider::createCompressedTexture(SkISize dimensions, const GrBackendFormat &format,
    skgpu::Budgeted budgeted, skgpu::Mipmapped mipmapped, GrProtected isProtected, SkData *data, std::string_view label)
{
    ASSERT_SINGLE_OWNER
    if (this->isAbandoned()) {
        return nullptr;
    }
    return fGpu->createCompressedTexture(dimensions, format, budgeted, mipmapped, isProtected, data->data(),
        data->size());
}

sk_sp<GrTexture> GrResourceProvider::createTexture(SkISize dimensions, const GrBackendFormat &format,
    GrTextureType textureType, GrRenderable renderable, int renderTargetSampleCnt, skgpu::Mipmapped mipmapped,
    skgpu::Budgeted budgeted, GrProtected isProtected, std::string_view label)
{
    ASSERT_SINGLE_OWNER
    if (this->isAbandoned()) {
        return nullptr;
    }

    if (!fCaps->validateSurfaceParams(dimensions, format, renderable, renderTargetSampleCnt, mipmapped, textureType)) {
        return nullptr;
    }

    // Currently we don't recycle compressed textures as scratch. Additionally all compressed
    // textures should be created through the createCompressedTexture function.
    SkASSERT(!this->caps()->isFormatCompressed(format));

    // TODO: Support skgpu::Mipmapped::kYes in scratch texture lookup here.
    sk_sp<GrTexture> tex = this->getExactScratch(dimensions, format, textureType, renderable, renderTargetSampleCnt,
        budgeted, mipmapped, isProtected, label);
    if (tex) {
        return tex;
    }

    return fGpu->createTexture(dimensions, format, textureType, renderable, renderTargetSampleCnt, mipmapped, budgeted,
        isProtected, label);
}

sk_sp<GrTexture> GrResourceProvider::createApproxTexture(SkISize dimensions, const GrBackendFormat &format,
    GrTextureType textureType, GrRenderable renderable, int renderTargetSampleCnt, GrProtected isProtected,
    std::string_view label)
{
    ASSERT_SINGLE_OWNER

    if (this->isAbandoned()) {
        return nullptr;
    }

    // Currently we don't recycle compressed textures as scratch. Additionally all compressed
    // textures should be created through the createCompressedTexture function.
    SkASSERT(!this->caps()->isFormatCompressed(format));

    if (!fCaps->validateSurfaceParams(dimensions, format, renderable, renderTargetSampleCnt, skgpu::Mipmapped::kNo,
        textureType)) {
        return nullptr;
    }

    auto copyDimensions = skgpu::GetApproxSize(dimensions);

    if (auto tex = this->findAndRefScratchTexture(copyDimensions, format, textureType, renderable,
        renderTargetSampleCnt, skgpu::Mipmapped::kNo, isProtected, label)) {
        return tex;
    }

    return fGpu->createTexture(copyDimensions, format, textureType, renderable, renderTargetSampleCnt,
        skgpu::Mipmapped::kNo, skgpu::Budgeted::kYes, isProtected, label);
}

sk_sp<GrTexture> GrResourceProvider::findAndRefScratchTexture(const skgpu::ScratchKey &key, std::string_view label)
{
    ASSERT_SINGLE_OWNER
    SkASSERT(!this->isAbandoned());
    SkASSERT(key.isValid());

    if (GrGpuResource *resource = fCache->findAndRefScratchResource(key)) {
        fGpu->stats()->incNumScratchTexturesReused();
        GrSurface *surface = static_cast<GrSurface *>(resource);
        resource->setLabel(std::move(label));
        return sk_sp<GrTexture>(surface->asTexture());
    }
    return nullptr;
}

sk_sp<GrTexture> GrResourceProvider::findAndRefScratchTexture(SkISize dimensions, const GrBackendFormat &format,
    GrTextureType textureType, GrRenderable renderable, int renderTargetSampleCnt, skgpu::Mipmapped mipmapped,
    GrProtected isProtected, std::string_view label)
{
    ASSERT_SINGLE_OWNER
    SkASSERT(!this->isAbandoned());
    SkASSERT(!this->caps()->isFormatCompressed(format));
    SkASSERT(fCaps->validateSurfaceParams(dimensions, format, renderable, renderTargetSampleCnt, skgpu::Mipmapped::kNo,
        textureType));

    // We could make initial clears work with scratch textures but it is a rare case so we just opt
    // to fall back to making a new texture.
    if (fGpu->caps()->reuseScratchTextures() || renderable == GrRenderable::kYes) {
        skgpu::ScratchKey key;
        GrTexture::ComputeScratchKey(*this->caps(), format, dimensions, renderable, renderTargetSampleCnt, mipmapped,
            isProtected, &key);
        return this->findAndRefScratchTexture(key, label);
    }

    return nullptr;
}

sk_sp<GrTexture> GrResourceProvider::wrapBackendTexture(const GrBackendTexture &tex, GrWrapOwnership ownership,
    GrWrapCacheable cacheable, GrIOType ioType)
{
    ASSERT_SINGLE_OWNER
    if (this->isAbandoned()) {
        return nullptr;
    }
    return fGpu->wrapBackendTexture(tex, ownership, cacheable, ioType);
}

sk_sp<GrTexture> GrResourceProvider::wrapCompressedBackendTexture(const GrBackendTexture &tex,
    GrWrapOwnership ownership, GrWrapCacheable cacheable)
{
    ASSERT_SINGLE_OWNER
    if (this->isAbandoned()) {
        return nullptr;
    }

    return fGpu->wrapCompressedBackendTexture(tex, ownership, cacheable);
}


sk_sp<GrTexture> GrResourceProvider::wrapRenderableBackendTexture(const GrBackendTexture &tex, int sampleCnt,
    GrWrapOwnership ownership, GrWrapCacheable cacheable)
{
    ASSERT_SINGLE_OWNER
    if (this->isAbandoned()) {
        return nullptr;
    }
    return fGpu->wrapRenderableBackendTexture(tex, sampleCnt, ownership, cacheable);
}

sk_sp<GrRenderTarget> GrResourceProvider::wrapBackendRenderTarget(const GrBackendRenderTarget &backendRT)
{
    ASSERT_SINGLE_OWNER
    return this->isAbandoned() ? nullptr : fGpu->wrapBackendRenderTarget(backendRT);
}

sk_sp<GrRenderTarget> GrResourceProvider::wrapVulkanSecondaryCBAsRenderTarget(const SkImageInfo &imageInfo,
    const GrVkDrawableInfo &vkInfo)
{
    ASSERT_SINGLE_OWNER
    return this->isAbandoned() ? nullptr : fGpu->wrapVulkanSecondaryCBAsRenderTarget(imageInfo, vkInfo);
}

void GrResourceProvider::assignUniqueKeyToResource(const skgpu::UniqueKey &key, GrGpuResource *resource)
{
    ASSERT_SINGLE_OWNER
    if (this->isAbandoned() || !resource) {
        return;
    }
    resource->resourcePriv().setUniqueKey(key);
}

sk_sp<GrGpuResource> GrResourceProvider::findResourceByUniqueKey(const skgpu::UniqueKey &key)
{
    ASSERT_SINGLE_OWNER
    return this->isAbandoned() ? nullptr : sk_sp<GrGpuResource>(fCache->findAndRefUniqueResource(key));
}

sk_sp<const GrGpuBuffer> GrResourceProvider::findOrMakeStaticBuffer(GrGpuBufferType intendedType, size_t size,
    const void *staticData, const skgpu::UniqueKey &key)
{
    if (auto buffer = this->findByUniqueKey<GrGpuBuffer>(key)) {
        return buffer;
    }

    auto buffer = this->createBuffer(staticData, size, intendedType, kStatic_GrAccessPattern);
    if (!buffer) {
        return nullptr;
    }

    // We shouldn't bin and/or cache static buffers.
    SkASSERT(buffer->size() == size);
    SkASSERT(!buffer->resourcePriv().getScratchKey().isValid());

    buffer->resourcePriv().setUniqueKey(key);

    return buffer;
}

sk_sp<const GrGpuBuffer> GrResourceProvider::findOrMakeStaticBuffer(GrGpuBufferType intendedType, size_t size,
    const skgpu::UniqueKey &uniqueKey, InitializeBufferFn initializeBufferFn)
{
    if (auto buffer = this->findByUniqueKey<GrGpuBuffer>(uniqueKey)) {
        return buffer;
    }

    auto buffer = this->createBuffer(size, intendedType, kStatic_GrAccessPattern, ZeroInit::kNo);
    if (!buffer) {
        return nullptr;
    }

    // We shouldn't bin and/or cache static buffers.
    SkASSERT(buffer->size() == size);
    SkASSERT(!buffer->resourcePriv().getScratchKey().isValid());

    buffer->resourcePriv().setUniqueKey(uniqueKey);

    // Map the buffer. Use a staging buffer on the heap if mapping isn't supported.
    skgpu::VertexWriter vertexWriter = { buffer->map(), size };
    AutoTMalloc<char> stagingBuffer;
    if (!vertexWriter) {
        SkASSERT(!buffer->isMapped());
        vertexWriter = { stagingBuffer.reset(size), size };
    }

    initializeBufferFn(std::move(vertexWriter), size);

    if (buffer->isMapped()) {
        buffer->unmap();
    } else {
        buffer->updateData(stagingBuffer, /* offset= */ 0, size, /* preserve= */ false);
    }

    return buffer;
}

sk_sp<const GrGpuBuffer> GrResourceProvider::createPatternedIndexBuffer(const uint16_t *pattern, int patternSize,
    int reps, int vertCount, const skgpu::UniqueKey *key)
{
    size_t bufferSize = patternSize * reps * sizeof(uint16_t);

    sk_sp<GrGpuBuffer> buffer =
        this->createBuffer(bufferSize, GrGpuBufferType::kIndex, kStatic_GrAccessPattern, ZeroInit::kNo);
    if (!buffer) {
        return nullptr;
    }
    uint16_t *data = (uint16_t *)buffer->map();
    AutoTArray<uint16_t> temp;
    if (!data) {
        temp.reset(reps * patternSize);
        data = temp.get();
    }
    for (int i = 0; i < reps; ++i) {
        int baseIdx = i * patternSize;
        uint16_t baseVert = (uint16_t)(i * vertCount);
        for (int j = 0; j < patternSize; ++j) {
            data[baseIdx + j] = baseVert + pattern[j];
        }
    }
    if (temp.get()) {
        if (!buffer->updateData(data, /* offset= */ 0, bufferSize, /* preserve= */ false)) {
            return nullptr;
        }
    } else {
        buffer->unmap();
    }
    if (key) {
        SkASSERT(key->isValid());
        this->assignUniqueKeyToResource(*key, buffer.get());
    }
    return buffer;
}

// /////////////////////////////////////////////////////////////////////////////////////////////////
static constexpr int kMaxNumNonAAQuads = 1 << 12; // max possible: (1 << 14) - 1;
static const int kVertsPerNonAAQuad = 4;
static const int kIndicesPerNonAAQuad = 6;

sk_sp<const GrGpuBuffer> GrResourceProvider::createNonAAQuadIndexBuffer()
{
    static_assert(kVertsPerNonAAQuad * kMaxNumNonAAQuads <= 65535); // indices fit in a uint16_t

    static const uint16_t kNonAAQuadIndexPattern[] = {
        0, 1, 2, 2, 1, 3
    };

    static_assert(std::size(kNonAAQuadIndexPattern) == kIndicesPerNonAAQuad);

    return this->createPatternedIndexBuffer(kNonAAQuadIndexPattern, kIndicesPerNonAAQuad, kMaxNumNonAAQuads,
        kVertsPerNonAAQuad, nullptr);
}

int GrResourceProvider::MaxNumNonAAQuads()
{
    return kMaxNumNonAAQuads;
}
int GrResourceProvider::NumVertsPerNonAAQuad()
{
    return kVertsPerNonAAQuad;
}
int GrResourceProvider::NumIndicesPerNonAAQuad()
{
    return kIndicesPerNonAAQuad;
}

// /////////////////////////////////////////////////////////////////////////////////////////////////
static constexpr int kMaxNumAAQuads = 1 << 9; // max possible: (1 << 13) - 1;
static const int kVertsPerAAQuad = 8;
static const int kIndicesPerAAQuad = 30;

sk_sp<const GrGpuBuffer> GrResourceProvider::createAAQuadIndexBuffer()
{
    static_assert(kVertsPerAAQuad * kMaxNumAAQuads <= 65535); // indices fit in a uint16_t

    // clang-format off
    static const uint16_t kAAQuadIndexPattern[] = {
        0, 1, 2, 1, 3, 2,
        0, 4, 1, 4, 5, 1,
        0, 6, 4, 0, 2, 6,
        2, 3, 6, 3, 7, 6,
        1, 5, 3, 3, 5, 7,
    };
    // clang-format on

    static_assert(std::size(kAAQuadIndexPattern) == kIndicesPerAAQuad);

    return this->createPatternedIndexBuffer(kAAQuadIndexPattern, kIndicesPerAAQuad, kMaxNumAAQuads, kVertsPerAAQuad,
        nullptr);
}

int GrResourceProvider::MaxNumAAQuads()
{
    return kMaxNumAAQuads;
}
int GrResourceProvider::NumVertsPerAAQuad()
{
    return kVertsPerAAQuad;
}
int GrResourceProvider::NumIndicesPerAAQuad()
{
    return kIndicesPerAAQuad;
}

// /////////////////////////////////////////////////////////////////////////////////////////////////

sk_sp<GrGpuBuffer> GrResourceProvider::createBuffer(size_t size, GrGpuBufferType intendedType,
    GrAccessPattern accessPattern, ZeroInit zeroInit)
{
    if (this->isAbandoned()) {
        return nullptr;
    }
    if (kDynamic_GrAccessPattern != accessPattern) {
        if (this->caps()->buffersAreInitiallyZero()) {
            zeroInit = ZeroInit::kNo;
        }
        sk_sp<GrGpuBuffer> buffer = this->gpu()->createBuffer(size, intendedType, accessPattern);
        if (buffer && zeroInit == ZeroInit::kYes && !buffer->clearToZero()) {
            return nullptr;
        }
        return buffer;
    }
    // bin by pow2+midpoint with a reasonable min
    static const size_t MIN_SIZE = 1 << 12;
    static const size_t MIN_UNIFORM_SIZE = 1 << 7;
    size_t allocSize =
        intendedType == GrGpuBufferType::kUniform ? std::max(size, MIN_UNIFORM_SIZE) : std::max(size, MIN_SIZE);
    size_t ceilPow2 = GrNextSizePow2(allocSize);
    size_t floorPow2 = ceilPow2 >> 1;
    size_t mid = floorPow2 + (floorPow2 >> 1);
    allocSize = (allocSize <= mid) ? mid : ceilPow2;

    skgpu::ScratchKey key;
    GrGpuBuffer::ComputeScratchKeyForDynamicBuffer(allocSize, intendedType, &key);
    auto buffer = sk_sp<GrGpuBuffer>(static_cast<GrGpuBuffer *>(this->cache()->findAndRefScratchResource(key)));
    if (!buffer) {
        if (this->caps()->buffersAreInitiallyZero()) {
            zeroInit = ZeroInit::kNo;
        }
        buffer = this->gpu()->createBuffer(allocSize, intendedType, kDynamic_GrAccessPattern);
    }
    if (buffer && zeroInit == ZeroInit::kYes && !buffer->clearToZero()) {
        return nullptr;
    }
    return buffer;
}

sk_sp<GrGpuBuffer> GrResourceProvider::createBuffer(const void *data, size_t size, GrGpuBufferType type,
    GrAccessPattern pattern)
{
    SkASSERT(data);
    auto buffer = this->createBuffer(size, type, pattern, ZeroInit::kNo);
    if (!buffer) {
        return nullptr;
    }
    if (!buffer->updateData(data, /* offset= */ 0, size, /* preserve= */ false)) {
        return nullptr;
    }
    return buffer;
}

static int num_stencil_samples(const GrRenderTarget *rt, bool useMSAASurface, const GrCaps &caps)
{
    int numSamples = rt->numSamples();
    if (numSamples == 1 && useMSAASurface) { // Are we using dynamic msaa?
        numSamples = caps.internalMultisampleCount(rt->backendFormat());
        SkASSERT(numSamples > 1); // Caller must ensure dmsaa is supported before trying to use it.
    }
    return numSamples;
}

bool GrResourceProvider::attachStencilAttachment(GrRenderTarget *rt, bool useMSAASurface)
{
    SkASSERT(rt);
    SkASSERT(!this->caps()->avoidStencilBuffers());

    GrAttachment *stencil = rt->getStencilAttachment(useMSAASurface);
    if (stencil) {
        SkASSERT(stencil->numSamples() == num_stencil_samples(rt, useMSAASurface, *this->caps()));
        return true;
    }

    if (!rt->wasDestroyed() && rt->canAttemptStencilAttachment(useMSAASurface)) {
        skgpu::UniqueKey sbKey;

#if 0
        if (this->caps()->oversizedStencilSupport()) {
            width  = SkNextPow2(width);
            height = SkNextPow2(height);
        }
#endif
        GrBackendFormat stencilFormat = this->gpu()->getPreferredStencilFormat(rt->backendFormat());
        if (!stencilFormat.isValid()) {
            return false;
        }
        GrProtected isProtected = rt->isProtected() ? GrProtected::kYes : GrProtected::kNo;
        int numStencilSamples = num_stencil_samples(rt, useMSAASurface, *this->caps());
        GrAttachment::ComputeSharedAttachmentUniqueKey(*this->caps(), stencilFormat, rt->dimensions(),
            GrAttachment::UsageFlags::kStencilAttachment, numStencilSamples, skgpu::Mipmapped::kNo, isProtected,
            GrMemoryless::kNo, &sbKey);
        auto keyedStencil = this->findByUniqueKey<GrAttachment>(sbKey);
        if (!keyedStencil) {
            // Need to try and create a new stencil
            keyedStencil = this->gpu()->makeStencilAttachment(rt->backendFormat(), rt->dimensions(), numStencilSamples);
            if (!keyedStencil) {
                return false;
            }
            this->assignUniqueKeyToResource(sbKey, keyedStencil.get());
        }
        rt->attachStencilAttachment(std::move(keyedStencil), useMSAASurface);
    }
    stencil = rt->getStencilAttachment(useMSAASurface);
    SkASSERT(!stencil || stencil->numSamples() == num_stencil_samples(rt, useMSAASurface, *this->caps()));
    return stencil != nullptr;
}

sk_sp<GrAttachment> GrResourceProvider::getDiscardableMSAAAttachment(SkISize dimensions, const GrBackendFormat &format,
    int sampleCnt, GrProtected isProtected, GrMemoryless memoryless)
{
    ASSERT_SINGLE_OWNER

    SkASSERT(sampleCnt > 1);

    if (this->isAbandoned()) {
        return nullptr;
    }

    if (!fCaps->validateSurfaceParams(dimensions, format, GrRenderable::kYes, sampleCnt, skgpu::Mipmapped::kNo,
        GrTextureType::kNone)) {
        return nullptr;
    }

    skgpu::UniqueKey key;
    GrAttachment::ComputeSharedAttachmentUniqueKey(*this->caps(), format, dimensions,
        GrAttachment::UsageFlags::kColorAttachment, sampleCnt, skgpu::Mipmapped::kNo, isProtected, memoryless, &key);
    auto msaaAttachment = this->findByUniqueKey<GrAttachment>(key);
    if (msaaAttachment) {
        return msaaAttachment;
    }
    msaaAttachment = this->makeMSAAAttachment(dimensions, format, sampleCnt, isProtected, memoryless);
    if (msaaAttachment) {
        this->assignUniqueKeyToResource(key, msaaAttachment.get());
    }
    return msaaAttachment;
}

sk_sp<GrAttachment> GrResourceProvider::makeMSAAAttachment(SkISize dimensions, const GrBackendFormat &format,
    int sampleCnt, GrProtected isProtected, GrMemoryless memoryless)
{
    ASSERT_SINGLE_OWNER

    SkASSERT(sampleCnt > 1);

    if (this->isAbandoned()) {
        return nullptr;
    }

    if (!fCaps->validateSurfaceParams(dimensions, format, GrRenderable::kYes, sampleCnt, skgpu::Mipmapped::kNo,
        GrTextureType::kNone)) {
        return nullptr;
    }

    auto scratch = this->refScratchMSAAAttachment(dimensions, format, sampleCnt, isProtected, memoryless,
        /* label= */ "MakeMSAAAttachment");
    if (scratch) {
        return scratch;
    }

    return fGpu->makeMSAAAttachment(dimensions, format, sampleCnt, isProtected, memoryless);
}

sk_sp<GrAttachment> GrResourceProvider::refScratchMSAAAttachment(SkISize dimensions, const GrBackendFormat &format,
    int sampleCnt, GrProtected isProtected, GrMemoryless memoryless, std::string_view label)
{
    ASSERT_SINGLE_OWNER
    SkASSERT(!this->isAbandoned());
    SkASSERT(!this->caps()->isFormatCompressed(format));
    SkASSERT(fCaps->validateSurfaceParams(dimensions, format, GrRenderable::kYes, sampleCnt, skgpu::Mipmapped::kNo,
        GrTextureType::kNone));

    skgpu::ScratchKey key;
    GrAttachment::ComputeScratchKey(*this->caps(), format, dimensions, GrAttachment::UsageFlags::kColorAttachment,
        sampleCnt, skgpu::Mipmapped::kNo, isProtected, memoryless, &key);
    GrGpuResource *resource = fCache->findAndRefScratchResource(key);
    if (resource) {
        fGpu->stats()->incNumScratchMSAAAttachmentsReused();
        GrAttachment *attachment = static_cast<GrAttachment *>(resource);
        resource->setLabel(std::move(label));
        return sk_sp<GrAttachment>(attachment);
    }

    return nullptr;
}

[[nodiscard]] std::unique_ptr<GrSemaphore> GrResourceProvider::makeSemaphore(bool isOwned)
{
    return this->isAbandoned() ? nullptr : fGpu->makeSemaphore(isOwned);
}

std::unique_ptr<GrSemaphore> GrResourceProvider::wrapBackendSemaphore(const GrBackendSemaphore &semaphore,
    GrSemaphoreWrapType wrapType, GrWrapOwnership ownership)
{
    ASSERT_SINGLE_OWNER
    return this->isAbandoned() ? nullptr : fGpu->wrapBackendSemaphore(semaphore, wrapType, ownership);
}

// Ensures the row bytes are populated (not 0) and makes a copy to a temporary
// to make the row bytes tight if necessary. Returns false if the input row bytes are invalid.
static bool prepare_level(const GrMipLevel &inLevel, SkISize dimensions, bool rowBytesSupport,
    GrColorType origColorType, GrColorType allowedColorType, GrMipLevel *outLevel, std::unique_ptr<char[]> *data)
{
    if (!inLevel.fPixels) {
        outLevel->fPixels = nullptr;
        outLevel->fRowBytes = 0;
        return true;
    }
    size_t minRB = dimensions.fWidth * GrColorTypeBytesPerPixel(origColorType);
    size_t actualRB = inLevel.fRowBytes ? inLevel.fRowBytes : minRB;
    if (actualRB < minRB) {
        return false;
    }
    if (origColorType == allowedColorType && (actualRB == minRB || rowBytesSupport)) {
        outLevel->fRowBytes = actualRB;
        outLevel->fPixels = inLevel.fPixels;
        return true;
    }
    auto tempRB = dimensions.fWidth * GrColorTypeBytesPerPixel(allowedColorType);
    data->reset(new char[tempRB * dimensions.fHeight]);
    outLevel->fPixels = data->get();
    outLevel->fRowBytes = tempRB;
    GrImageInfo srcInfo(origColorType, kUnpremul_SkAlphaType, nullptr, dimensions);
    GrImageInfo dstInfo(allowedColorType, kUnpremul_SkAlphaType, nullptr, dimensions);
    return GrConvertPixels(GrPixmap(dstInfo, data->get(), tempRB), GrCPixmap(srcInfo, inLevel.fPixels, actualRB));
}

GrColorType GrResourceProvider::prepareLevels(const GrBackendFormat &format, GrColorType colorType, SkISize baseSize,
    const GrMipLevel texels[], int mipLevelCount, TempLevels *tempLevels, TempLevelDatas *tempLevelDatas) const
{
    SkASSERT(mipLevelCount && texels && texels[0].fPixels);

    auto allowedColorType = this->caps()->supportedWritePixelsColorType(colorType, format, colorType).fColorType;
    if (allowedColorType == GrColorType::kUnknown) {
        return GrColorType::kUnknown;
    }
    bool rowBytesSupport = this->caps()->writePixelsRowBytesSupport();
    tempLevels->reset(mipLevelCount);
    tempLevelDatas->reset(mipLevelCount);
    auto size = baseSize;
    for (int i = 0; i < mipLevelCount; ++i) {
        if (!prepare_level(texels[i], size, rowBytesSupport, colorType, allowedColorType, &(*tempLevels)[i],
            &(*tempLevelDatas)[i])) {
            return GrColorType::kUnknown;
        }
        size = { std::max(size.fWidth / 2, 1), std::max(size.fHeight / 2, 1) };
    }
    return allowedColorType;
}

sk_sp<GrTexture> GrResourceProvider::writePixels(sk_sp<GrTexture> texture, GrColorType colorType, SkISize baseSize,
    const GrMipLevel texels[], int mipLevelCount) const
{
    SkASSERT(!this->isAbandoned());
    SkASSERT(texture);
    SkASSERT(colorType != GrColorType::kUnknown);
    SkASSERT(mipLevelCount && texels && texels[0].fPixels);

    AutoSTArray<14, GrMipLevel> tmpTexels;
    AutoSTArray<14, std::unique_ptr<char[]>> tmpDatas;
    auto tempColorType = this->prepareLevels(texture->backendFormat(), colorType, baseSize, texels, mipLevelCount,
        &tmpTexels, &tmpDatas);
    if (tempColorType == GrColorType::kUnknown) {
        return nullptr;
    }
    SkAssertResult(fGpu->writePixels(texture.get(), SkIRect::MakeSize(baseSize), colorType, tempColorType,
        tmpTexels.get(), mipLevelCount));
    return texture;
}
